/**
 * Copyright (C) 2009+ Spark Zheng
 *
 * @file        src/utils.h 
 * @date        2010-04-05
 * @brief	general_network
 * @see         dserver
 * @history
 */

#ifndef _DSERVER_UTILS_H
#define _DSERVER_UTILS_H

#ifdef __cplusplus
extern "C" {
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include "prot_types.h"
#include "dserver_errno.h"

#ifndef INET_ADDRSTRLEN
#define INET_ADDRSTRLEN		16
#endif
#ifndef INET_PORTSTRLEN
#define INET_PORTSTRLEN		8
#endif

static inline int dserver_set_nonblock(int fd)
{
	int flag = 0;
	if ((flag = fcntl(fd, F_GETFL, 0)) < 0 || fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	return 0;
}

static inline int dserver_set_reuseaddr(int fd)
{
	int flag = 1;
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	return 0;
}

static inline int dserver_set_rcv_timeout(int fd, time_t sec, uint32_t usec)
{
	struct timeval rcv_timeo = {sec, usec};
	if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &rcv_timeo, sizeof(rcv_timeo)) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	return 0;
}

static inline int dserver_set_snd_timeout(int fd, time_t sec, uint32_t usec)
{
	struct timeval snd_timeo = {sec, usec};
	if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &snd_timeo, sizeof(snd_timeo)) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	return 0;
}

static inline int dserver_set_tcp_nodelay(int fd)
{
	int tcp_nodelay = 1;
	if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(tcp_nodelay)) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	return 0;
}

static inline int dserver_set_tcp_keepalive(int fd)
{
	int tcp_keepalive = 1;
	if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &tcp_keepalive, sizeof(tcp_keepalive)) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	return 0;
}

static inline int dserver_set_tcp_linger(int fd, int onoff, int nr)
{
	struct linger tcp_linger = {onoff, nr};
	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &tcp_linger, sizeof(tcp_linger)) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	return 0;
}

static inline int dserver_set_udp_rcvbuf(int fd, int buf_size)
{
	int32_t rcvbuf = 0;
	size_t len = sizeof(rcvbuf);
	if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) < 0) {
	//	rcvbuf = DSERVER_UDP_RX_BUFLEN;
		return -E_UTILS_SOCKOPT;
	}
	if (buf_size <= rcvbuf) {
		return 1;
	}
	if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size)) < 0) {
		(void)setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
	}
	return 0;
}

static inline int dserver_set_udp_sndbuf(int fd, int buf_size)
{
	int32_t sndbuf = 0;
	size_t len = sizeof(sndbuf);
	if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &len) < 0) {
	//	sndbuf = DSERVER_UDP_TX_BUFLEN;
		return -E_UTILS_SOCKOPT;
	}
	if (buf_size <= sndbuf) {
		return 1;	
	}
	if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)) < 0) {
		(void)setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
	}
	return 0;
}

static inline int dserver_tcp_send_reset(int fd)
{
	struct linger tcp_linger = {1, 0};
	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &tcp_linger, sizeof(tcp_linger)) < 0) {
		return -E_UTILS_SOCKOPT;
	}
	if (close(fd) < 0) {
		return -E_UTILS_CLOSE;
	}
	return 0;
}

static inline int dserver_wait_sock(int fd, uint16_t events, uint16_t timeo)
{
	int err;
	struct pollfd pfd;

	pfd.fd = fd;
	pfd.revents = 0;
	pfd.events = events;

	err = poll(&pfd, 1, timeo * 1000);
	if (err < 0) {
		return -E_EVENT;
	} 
	if (err == 0) {
		return -E_EVENT_NODATA;
	}

	if (pfd.revents & events) {
		return 0;
	}

	return -E_EVENT;
}

static inline int dserver_wait_write(int fd, uint16_t timeo)
{
	return dserver_wait_sock(fd, POLLWRNORM, timeo);
}

static inline int dserver_wait_read(int fd, uint16_t timeo)
{
	return dserver_wait_sock(fd, POLLRDNORM, timeo);
}

/** dserver server/client socket api */
/**
 * dserver_server_new
 * @brief create a new server bind on domain:port
 * @return <0 error
 * @param domain: binding host
 * @param port: binding port
 * @param type: server type, TCP/UDP/SCTP_DGRAM/SCTP_STREAM
 */
extern int dserver_server_new(const char *domain, in_port_t port, prot_type_t type);

/**
 * dserver_tcp_client_new
 * @brief create a new tcp client connect to domain:port
 * @return <0 error
 * @param domain: connecting host, could not be NULL when type is TCP
 * @param port: connecting port, could not be 0 when type is TCP
 * @param timeo: timeout number
 */
extern int dserver_tcp_client_new(const char *domain, in_port_t port, uint16_t timeo);

/**
 * dserver_udp_client_new
 * @brief create a new udp client connect to domain:port
 * @return <0 error
 * @param domain: connecting host, could be NULL when type is UDP/SCTP_DGRAM
 * @param port: connecting port, could be 0 when type is UDP/SCTP_DGRAM
 * @param timeo: timeout number
 */
extern int dserver_udp_client_new(const char *domain, in_port_t port, uint16_t timeo);

/** dserver server/client data api */
extern ssize_t dserver_data_write(int sockfd, const char *buf,
		size_t buf_len, uint16_t timeo);

extern ssize_t dserver_data_writev(int sockfd, const struct iovec *vec,
		uint16_t vec_nr, uint16_t timeo);

extern ssize_t dserver_data_read(int sockfd, char *buf, size_t buf_len,
		uint16_t timeo);

extern ssize_t dserver_data_readv(int sockfd, struct iovec *vec,
		uint16_t vec_nr, uint16_t timeo);

extern ssize_t dserver_data_sendfile(int infd, int outfd, size_t nr,
		off_t *from, off_t *to);

#ifdef __cplusplus
}
#endif
#endif
